import openai
import asyncio
from typing import Dict
from config import settings
from concurrent.futures import ThreadPoolExecutor

# 创建线程池用于执行同步的 OpenAI 流式响应
executor = ThreadPoolExecutor(max_workers=5)

class OpenAIService:
    """OpenAI 服务类 - 支持推理模型"""
    
    # 已知的推理模型列表
    REASONING_MODELS = [
        "deepseek-chat",
        "deepseek-reasoner",
        "qwen2.5-72b-instruct",
        "qwen2.5-32b-instruct",
        "gpt-4o",
        "gpt-4-turbo",
        "o1-preview",
        "o1-mini",
        "claude-3-5-sonnet",
        "claude-3-opus"
    ]
    
    def __init__(self):
        self.api_key = settings.OPENAI_API_KEY
        self.base_url = settings.OPENAI_BASE_URL
        self.analysis_model = settings.OPENAI_ANALYSIS_MODEL
        self.consultation_model = settings.OPENAI_MODEL
        self.enable_reasoning = settings.ENABLE_REASONING
        self.reasoning_effort = settings.REASONING_EFFORT
        self.use_reasoning_model = settings.USE_REASONING_MODEL
        self.client = None
        
        if self.api_key and self.api_key.strip():
            try:
                print(f"API Key: {self.api_key[:10]}...")
                print(f"Base URL: {self.base_url}")
                print(f"Analysis Model: {self.analysis_model}")
                print(f"Consultation Model: {self.consultation_model}")
                print(f"推理模式: {'启用' if self.enable_reasoning or self._is_reasoning_model(self.analysis_model) else '禁用'}")
                self.client = openai.OpenAI(base_url=self.base_url, api_key=self.api_key)
            except Exception as e:
                print(f"OpenAI 客户端初始化失败: {e}")
                self.client = None
    
    def _is_reasoning_model(self, model: str) -> bool:
        """判断是否为推理模型"""
        model_lower = model.lower()
        return any(reasoning_model in model_lower for reasoning_model in self.REASONING_MODELS)
    
    def _get_reasoning_params(self, model: str) -> Dict:
        """获取推理模型的参数"""
        params = {}
        model_lower = model.lower()
        
        # 如果明确启用推理模式，或使用推理模型
        if self.enable_reasoning or self._is_reasoning_model(model):
            # DeepSeek 推理模型参数
            if "deepseek" in model_lower:
                if "reasoner" in model_lower:
                    # DeepSeek Reasoner 需要 reasoning_effort 参数
                    params["reasoning_effort"] = self.reasoning_effort
                elif "chat" in model_lower:
                    # DeepSeek Chat 可能支持推理增强
                    # 某些版本的 DeepSeek Chat 支持 reasoning_effort
                    if self.enable_reasoning:
                        params["reasoning_effort"] = self.reasoning_effort
            
            # Qwen 推理模型参数
            elif "qwen" in model_lower:
                # Qwen 模型通常通过提示词启用推理
                # 某些API提供商可能支持特殊参数，这里保留扩展性
                pass
            
            # OpenAI o1 系列模型
            elif "o1" in model_lower:
                # o1 系列是专门的推理模型，不需要额外参数
                # 注意：o1 系列不支持 temperature 参数
                pass
            
            # Claude 推理模型
            elif "claude" in model_lower:
                # Claude 模型通过提示词和系统消息启用推理
                # Claude 可能支持 max_tokens 等参数优化
                pass
        
        return params
    
    def _should_use_temperature(self, model: str) -> bool:
        """判断模型是否支持 temperature 参数"""
        model_lower = model.lower()
        # o1 系列不支持 temperature
        if "o1" in model_lower:
            return False
        # 其他模型通常支持
        return True
    
    def _analyze_bazi_stream_sync(self, bazi_data: Dict[str, str], five_elements: Dict[str, int], 
                                   birth_info: Dict[str, int], name: str = None, gender: str = None):
        """同步版本的流式分析（在线程池中运行）"""
        if not self.client:
            content = self._get_default_analysis(bazi_data, five_elements, birth_info, name, gender)
            yield content
            return
        
        print('构建提示词')
        # 构建提示词
        prompt = self._build_analysis_prompt(bazi_data, five_elements, birth_info, name, gender)
        print(prompt)
        try:
            # 构建消息
            messages = [
                {
                    "role": "system",
                    "content": self._build_system_message(self.analysis_model, "analysis")
                },
                {
                    "role": "user",
                    "content": prompt
                }
            ]
            
            # 构建请求参数
            request_params = {
                "model": self.analysis_model,
                "messages": messages,
                "max_tokens": 2000,
                "stream": True,  # 启用流式响应
            }
            
            # 添加推理参数
            reasoning_params = self._get_reasoning_params(self.analysis_model)
            request_params.update(reasoning_params)
            
            # 某些模型不支持 temperature 参数
            if self._should_use_temperature(self.analysis_model):
                request_params["temperature"] = 0.7
            
            print(f"调用模型: {self.analysis_model}, 推理参数: {reasoning_params}")
            
            stream = self.client.chat.completions.create(**request_params)
            
            # 处理流式响应
            full_content = ""
            buffer = ""  # 用于缓冲可能包含标签的内容
            in_reasoning = False
            has_reasoning_tag = False  # 是否遇到过 <think> 标签
            can_stream = True  # 默认允许流式输出，除非遇到 <think>
            chunk_count = 0
            
            # 使用同步迭代处理流（OpenAI Python SDK 的流式响应是同步的）
            for chunk in stream:
                chunk_count += 1
                if chunk.choices and len(chunk.choices) > 0:
                    delta = chunk.choices[0].delta
                    
                    # 检查是否是推理内容（o1系列模型）
                    if hasattr(delta, 'reasoning_content') and delta.reasoning_content:
                        # o1系列的推理内容，直接跳过
                        continue
                    
                    # 获取正常内容
                    if hasattr(delta, 'content') and delta.content:
                        content = delta.content
                        
                        # 过滤掉 ```markdown 这样的代码块标记
                        content = content.replace('```markdown', '').replace('```', '')
                        
                        buffer += content
                        full_content += content
                        
                        # 检查是否包含 <think> 标签
                        if '<think>' in buffer:
                            has_reasoning_tag = True
                            in_reasoning = True
                            can_stream = False  # 进入推理标签，停止流式输出
                            # 移除标签之前的内容（如果有）
                            tag_start = buffer.find('<think>')
                            if tag_start > 0:
                                # 标签前有内容，先发送这部分
                                before_tag = buffer[:tag_start]
                                yield before_tag
                            # 移除标签本身和之前的所有内容
                            buffer = buffer[tag_start + len('<think>'):]
                        
                        # 检查是否包含 </think> 标签
                        if '</think>' in buffer:
                            # 找到结束标签位置
                            tag_end = buffer.find('</think>')
                            # 移除标签及其之前的所有内容（推理内容）
                            buffer = buffer[tag_end + len('</think>'):]
                            in_reasoning = False
                            can_stream = True  # 遇到结束标签后，允许流式输出
                            # 如果标签后有内容，立即发送
                            if buffer:
                                yield buffer
                                buffer = ""
                            continue
                        
                        # 如果不在推理标签内且允许流式输出，发送内容
                        if not in_reasoning and can_stream:
                            # 发送缓冲的内容
                            if buffer:
                                yield buffer
                                buffer = ""
            
            # 如果还有缓冲内容且不在推理中，发送剩余内容
            if buffer and not in_reasoning:
                yield buffer
            
            # 如果整个响应都在推理标签内（遇到了开始标签但没遇到结束标签），返回默认分析
            if has_reasoning_tag and in_reasoning:
                # 整个响应都在推理标签内，返回默认分析
                print(f"警告：整个响应都在推理标签内，返回默认分析")
                default = self._get_default_analysis(bazi_data, five_elements, birth_info, name, gender)
                yield default
            elif not full_content:
                # 没有任何内容，返回默认分析
                print(f"警告：没有收到任何内容（chunk_count={chunk_count}），返回默认分析")
                default = self._get_default_analysis(bazi_data, five_elements, birth_info, name, gender)
                yield default
            else:
                print(f"流式响应完成，共收到 {chunk_count} 个chunk，内容长度: {len(full_content)}")
                
        except Exception as e:
            print(f"OpenAI 聊天请求失败: {e}")
            import traceback
            traceback.print_exc()
            default = self._get_default_analysis(bazi_data, five_elements, birth_info, name, gender)
            yield default
    
    async def analyze_bazi_stream(self, bazi_data: Dict[str, str], five_elements: Dict[str, int], 
                                  birth_info: Dict[str, int], name: str = None, gender: str = None):
        """使用 OpenAI 流式分析生辰八字（异步包装）"""
        # 创建同步生成器
        sync_gen = self._analyze_bazi_stream_sync(bazi_data, five_elements, birth_info, name, gender)
        
        # 使用特殊标记表示生成器结束
        _SENTINEL = object()
        
        # 将同步生成器转换为异步生成器
        def get_next_chunk(gen):
            """包装函数，用于在线程池中获取生成器的下一个值"""
            try:
                return next(gen)
            except StopIteration:
                # 返回特殊标记，而不是抛出异常
                return _SENTINEL
        
        loop = asyncio.get_event_loop()
        while True:
            try:
                # 在线程池中执行下一个值的获取，避免阻塞事件循环
                chunk = await loop.run_in_executor(executor, get_next_chunk, sync_gen)
                
                # 检查是否生成器已结束
                if chunk is _SENTINEL:
                    break
                
                yield chunk
                # 让出控制权，允许其他协程运行
                await asyncio.sleep(0)
            except Exception as e:
                print(f"流式响应处理错误: {e}")
                import traceback
                traceback.print_exc()
                # 返回默认分析
                default = self._get_default_analysis(bazi_data, five_elements, birth_info, name, gender)
                yield default
                break
    
    async def analyze_bazi(self, bazi_data: Dict[str, str], five_elements: Dict[str, int], 
                          birth_info: Dict[str, int], name: str = None, gender: str = None) -> str:
        """使用 OpenAI 分析生辰八字（非流式，用于兼容）"""
        content = ""
        async for chunk in self.analyze_bazi_stream(bazi_data, five_elements, birth_info, name, gender):
            content += chunk
        return content
    
    def _build_analysis_prompt(self, bazi_data: Dict[str, str], five_elements: Dict[str, int], 
                              birth_info: Dict[str, int], name: str = None, gender: str = None) -> str:
        """构建分析提示词"""
        
        gender_text = f"性别：{gender}，" if gender else ""
        name_text = f"姓名：{name}，" if name else ""
        
        prompt = f"""
请作为一位资深命理大师，分析以下生辰八字信息，重点关注现代年轻人的婚嫁和事业运势，使用 Markdown 格式输出：

{name_text}{gender_text}出生时间：{birth_info['year']}年{birth_info['month']}月{birth_info['day']}日{birth_info['hour']}时{birth_info['minute']}分

四柱八字：
- 年柱：{bazi_data['year_pillar']}
- 月柱：{bazi_data['month_pillar']}
- 日柱：{bazi_data['day_pillar']}
- 时柱：{bazi_data['hour_pillar']}

五行分布：
- 木：{five_elements['木']}个
- 火：{five_elements['火']}个
- 土：{five_elements['土']}个
- 金：{five_elements['金']}个
- 水：{five_elements['水']}个

请使用 Markdown 格式，从以下几个方面进行详细分析，**特别突出婚嫁和事业两个部分**：

## 📊 命格总览
简要概括整体命格特点（2-3句话）

## 💑 婚嫁运势（重点分析）
这是重点部分，请详细分析：
- **感情特点**：性格中的感情倾向，恋爱方式
- **理想伴侣**：适合的伴侣类型、性格特点、五行互补
- **婚恋时机**：适合恋爱结婚的年龄段和时机
- **婚姻建议**：如何改善感情运势，增进夫妻关系
- **注意事项**：感情中需要注意的问题和化解方法
- 结合现代年轻人婚恋观，给出实用建议

## 💼 事业运势（重点分析）
这是重点部分，请详细分析：
- **职业方向**：适合的行业、职业类型
- **事业发展**：事业发展的特点和趋势
- **工作方式**：适合的工作方式、团队合作还是独立工作
- **升职加薪**：事业上升的关键期和机遇
- **创业建议**：是否适合创业，如果创业需要注意什么
- 结合现代职场环境，给出实用建议

## 💰 财运分析
- 财运特点
- 理财建议
- 投资方向

## 🎯 性格特点
- 性格优势
- 性格不足
- 人际关系

## 💡 人生建议
- 总体运势建议
- 开运建议
- 注意事项

要求：
1. 使用 Markdown 格式，包含标题、列表、粗体等
2. 用通俗易懂的语言，结合传统文化智慧，但要用现代语言表达
3. 给出积极正面且实用的建议，避免空洞的理论
4. **婚嫁和事业部分必须详细，每个部分至少300字**
5. 适当使用 **粗体** 和 *斜体* 强调重点
6. 使用emoji增加可读性
7. 结合现代年轻人的生活实际，给出接地气的建议
"""
        return prompt
    
    async def answer_question_stream(self, bazi_data: Dict[str, str], question: str, 
                                     name: str = None, gender: str = None):
        """流式回答关于命理的问题"""
        
        if not self.client:
            yield "很抱歉，AI 问答功能暂时不可用。请检查 OpenAI API 配置或稍后再试。"
            return
        
        prompt = f"""
基于以下八字信息：
年柱：{bazi_data['year_pillar']}
月柱：{bazi_data['month_pillar']}
日柱：{bazi_data['day_pillar']}
时柱：{bazi_data['hour_pillar']}

{f"姓名：{name}，" if name else ""}{f"性别：{gender}，" if gender else ""}

用户问题：{question}

请作为命理专家，结合八字信息给出专业、详细且实用的回答。回答要：
1. 结合传统命理学理论
2. 用现代语言表达，通俗易懂
3. 给出实用的建议
4. 积极正面，富有建设性
5. 如果问题涉及婚嫁或事业，请重点分析
"""
        
        try:
            # 构建消息
            messages = [
                {
                    "role": "system",
                    "content": self._build_system_message(self.consultation_model, "consultation")
                },
                {
                    "role": "user",
                    "content": prompt
                }
            ]
            
            # 构建请求参数
            request_params = {
                "model": self.consultation_model,
                "messages": messages,
                "max_tokens": 1000,
                "stream": True,  # 启用流式响应
            }
            
            # 添加推理参数
            reasoning_params = self._get_reasoning_params(self.consultation_model)
            request_params.update(reasoning_params)
            
            # 某些模型不支持 temperature 参数
            if self._should_use_temperature(self.consultation_model):
                request_params["temperature"] = 0.7
            
            stream = self.client.chat.completions.create(**request_params)
            
            # 处理流式响应
            buffer = ""  # 用于缓冲可能包含标签的内容
            in_reasoning = False
            has_reasoning_tag = False  # 是否遇到过 <think> 标签
            can_stream = True  # 默认允许流式输出，除非遇到 <think>
            
            for chunk in stream:
                if chunk.choices and len(chunk.choices) > 0:
                    delta = chunk.choices[0].delta
                    
                    # 跳过推理内容（o1系列）
                    if hasattr(delta, 'reasoning_content') and delta.reasoning_content:
                        continue
                    
                    # 获取正常内容
                    if hasattr(delta, 'content') and delta.content:
                        content = delta.content
                        
                        # 过滤掉 ```markdown 这样的代码块标记
                        content = content.replace('```markdown', '').replace('```', '')
                        
                        buffer += content
                        
                        # 检查是否包含 <think> 标签
                        if '<think>' in buffer:
                            has_reasoning_tag = True
                            in_reasoning = True
                            can_stream = False  # 进入推理标签，停止流式输出
                            # 移除标签之前的内容（如果有）
                            tag_start = buffer.find('<think>')
                            if tag_start > 0:
                                # 标签前有内容，先发送这部分
                                before_tag = buffer[:tag_start]
                                yield before_tag
                            # 移除标签本身和之前的所有内容
                            buffer = buffer[tag_start + len('<think>'):]
                        
                        # 检查是否包含 </think> 标签
                        if '</think>' in buffer:
                            # 找到结束标签位置
                            tag_end = buffer.find('</think>')
                            # 移除标签及其之前的所有内容（推理内容）
                            buffer = buffer[tag_end + len('</think>'):]
                            in_reasoning = False
                            can_stream = True  # 遇到结束标签后，允许流式输出
                            # 如果标签后有内容，立即发送
                            if buffer:
                                yield buffer
                                buffer = ""
                            continue
                        
                        # 如果不在推理标签内且允许流式输出，发送内容
                        if not in_reasoning and can_stream:
                            # 发送缓冲的内容
                            if buffer:
                                yield buffer
                                buffer = ""
            
            # 如果还有缓冲内容且不在推理中，发送剩余内容
            if buffer and not in_reasoning:
                yield buffer
                        
        except Exception as e:
            import traceback
            traceback.print_exc()
            yield f"AI 问答暂时不可用，请稍后再试。错误信息：{str(e)}"
    
    async def answer_question(self, bazi_data: Dict[str, str], question: str, 
                              name: str = None, gender: str = None) -> str:
        """回答关于命理的问题（非流式，用于兼容）"""
        content = ""
        async for chunk in self.answer_question_stream(bazi_data, question, name, gender):
            content += chunk
        return content
    
    def _build_system_message(self, model: str, task_type: str) -> str:
        """构建系统消息，针对推理模型优化"""
        model_lower = model.lower()
        
        # 基础系统消息
        if task_type == "analysis":
            base_message = "你是一位精通中国传统命理学的资深专家，擅长分析生辰八字，特别关注现代年轻人的婚嫁和事业运势。你能够结合传统命理学智慧和现代生活实际，给出专业、详细、实用且富有启发性的命理分析。"
        else:  # consultation
            base_message = "你是一位资深的命理咨询师，擅长解答关于生辰八字的各种问题，特别关注婚嫁和事业方面。请根据八字信息为用户提供专业、实用的建议。"
        
        # 如果是推理模型，添加推理提示
        if self._is_reasoning_model(model) or self.enable_reasoning:
            reasoning_instruction = """
            
请使用你的推理能力，仔细分析八字信息，逐步思考：
1. 分析五行平衡关系
2. 分析天干地支的相互作用
3. 结合传统命理学理论进行推理
4. 给出有逻辑、有依据的分析结论
"""
            base_message += reasoning_instruction
        
        return base_message
    
    def _get_default_analysis(self, bazi_data: Dict[str, str], five_elements: Dict[str, int], 
                             birth_info: Dict[str, int], name: str = None, gender: str = None) -> str:
        """获取默认分析（当 OpenAI 不可用时）"""
        balance_status = '平衡' if max(five_elements.values()) - min(five_elements.values()) <= 1 else '不平衡'
        
        gender_text = f"性别：{gender}\n" if gender else ""
        name_text = f"姓名：{name}\n" if name else ""
        
        return f"""# 您的生辰八字分析

## 📊 基本信息
{name_text}{gender_text}**出生时间**：{birth_info['year']}年{birth_info['month']}月{birth_info['day']}日{birth_info['hour']}时{birth_info['minute']}分

## 四柱八字
- **年柱**：{bazi_data['year_pillar']}
- **月柱**：{bazi_data['month_pillar']}
- **日柱**：{bazi_data['day_pillar']}
- **时柱**：{bazi_data['hour_pillar']}

## 五行分布
- **木**：{five_elements['木']}个
- **火**：{five_elements['火']}个
- **土**：{five_elements['土']}个
- **金**：{five_elements['金']}个
- **水**：{five_elements['水']}个

## 💑 婚嫁运势
根据传统命理学，您的八字组合显示出独特的感情特征。五行分布相对**{balance_status}**，这会影响您的感情发展和婚姻运势。建议您保持真诚，寻找性格互补的伴侣。

## 💼 事业运势
您的事业运势与五行平衡密切相关。建议您根据自己的性格特点选择合适的职业方向，在事业发展的关键期把握机遇。

## 💡 人生建议
1. **保持积极乐观的心态**
2. **注重五行平衡的调节**
3. **根据自身特点选择发展方向**
4. **多行善事，积累福德**

> **注意**：此分析基于传统命理学原理，仅供参考娱乐，不应作为人生决策的唯一依据。如需更详细的分析，请使用AI分析功能。
"""
    
